{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Table tennis simulation\n",
    "\n",
    "This example shows the usage of `DiscreteEvents.jl` with event driven state machines.\n",
    "\n",
    "We implement players as timed state machines and thus need definitions of states and events and some data describing the players:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "using DiscreteEvents, Random, Printf\n",
    "\n",
    "abstract type PState end\n",
    "struct Idle <: PState end\n",
    "struct Wait <: PState end\n",
    "struct Unalert <: PState end\n",
    "\n",
    "abstract type PEvent end\n",
    "struct Start <: PEvent end\n",
    "struct Serve <: PEvent end\n",
    "struct Return <: PEvent end\n",
    "struct Miss <: PEvent end\n",
    "\n",
    "mutable struct Player\n",
    "    name::AbstractString\n",
    "    opp::Union{Number,Player}\n",
    "    state::PState\n",
    "    accuracy::Float64\n",
    "    attentiveness::Float64\n",
    "    score::Int64\n",
    "\n",
    "    Player(name, acc, att) = new(name, 0, Idle(), acc, att, 0)\n",
    "end"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then we define some physical facts and a function to randomize them:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "rd (generic function with 1 method)"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "const dist = 3 # distance for ball to fly [m]\n",
    "const vs   = 10 # serve velocity [m/s]\n",
    "const vr   = 20 # return velocity [m/s]\n",
    "\n",
    "rd(s::Float64) = randn()*s + 1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next we must describe the behaviour of our players. They are modeled as finite state machines, which have known states and react to known events. This is done with the `step!` function. Julia's multiple dispatch allows to give multiple definitions of `step!` for different combinations of states and events.\n",
    "\n",
    "The `serve` and `ret`-functions, used for describing serving and return of players are used to randomize the time and the behaviour of players. The players thus act probabilistically as Markov automata."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "ret (generic function with 1 method)"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "function init!(p::Player, opp::Player)\n",
    "    p.opp = opp\n",
    "    if rand() ≤ p.attentiveness\n",
    "        p.state = Wait()\n",
    "    else\n",
    "        p.state = Unalert()\n",
    "    end\n",
    "end\n",
    "\n",
    "function serve(p::Player)\n",
    "    ts = 3 + dist*rd(0.15)/(vs*rd(0.25))\n",
    "    if (rand() ≤ p.accuracy) && (p.state == Wait())\n",
    "        event!(𝐶, SF(step!, p.opp, Serve()), after, ts)\n",
    "        @printf(\"%5.2f: %s serves %s\\n\", tau()+ts, p.name, p.opp.name)\n",
    "    else\n",
    "        event!(𝐶, SF(step!, p.opp, Miss()), after, ts)\n",
    "        @printf(\"%5.2f: %s serves and misses %s\\n\", tau()+ts, p.name, p.opp.name)\n",
    "    end\n",
    "    if rand() ≥ p.attentiveness\n",
    "        p.state = Unalert()\n",
    "    end\n",
    "end\n",
    "\n",
    "function ret(p::Player)\n",
    "    tr = dist*rd(0.15)/(vr*rd(0.25))\n",
    "    if rand() ≤ p.accuracy\n",
    "        event!(𝐶, SF(step!, p.opp, Return()), after, tr)\n",
    "        @printf(\"%5.2f: %s returns %s\\n\", tau()+tr, p.name, p.opp.name)\n",
    "    else\n",
    "        event!(𝐶, SF(step!, p.opp, Miss()), after, tr)\n",
    "        @printf(\"%5.2f: %s returns and misses %s\\n\", tau()+tr, p.name, p.opp.name)\n",
    "    end\n",
    "    if rand() ≥ p.attentiveness\n",
    "        p.state = Unalert()\n",
    "    end\n",
    "end"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The actual behaviour of a player is implemented as a state machine via the `step!`--function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "step!"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\"default transition for players\"\n",
    "step!(p::Player, q::PState, σ::PEvent) =\n",
    "        println(\"undefined transition for $(p.name), $q, $σ\")\n",
    "\n",
    "\"player p gets a start command\"\n",
    "step!(p::Player, ::Union{Wait, Unalert}, ::Start) = serve(p)\n",
    "\n",
    "\"player p is waiting and gets served or returned\"\n",
    "step!(p::Player, ::Wait, ::Union{Serve, Return}) = ret(p)\n",
    "\n",
    "\"player p is unalert and gets served or returned\"\n",
    "function step!(p::Player, ::Unalert, ::Union{Serve, Return})\n",
    "    @printf(\"%5.2f: %s looses ball\\n\", τ(), p.name)\n",
    "    p.opp.score += 1\n",
    "    p.state = Wait()\n",
    "    serve(p)\n",
    "end\n",
    "\n",
    "\"player p is waiting or unalert and gets missed\"\n",
    "function step!(p::Player, ::Union{Wait, Unalert}, ::Miss)\n",
    "    p.score += 1\n",
    "    p.state = Wait()\n",
    "    serve(p)\n",
    "end\n",
    "\n",
    "\"simplified `step!` call\"\n",
    "step!(p::Player, σ::PEvent) = step!(p, p.state, σ)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In order to setup a simulation, we have to create and initialize the players, to start and run the game:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " 3.33: Ping serves Pong\n",
      " 3.33: Pong looses ball\n",
      " 6.57: Pong serves Ping\n",
      " 6.74: Ping returns Pong\n",
      " 6.90: Pong returns Ping\n",
      " 7.02: Ping returns Pong\n",
      " 7.20: Pong returns Ping\n",
      " 7.32: Ping returns Pong\n",
      " 7.43: Pong returns Ping\n",
      " 7.54: Ping returns Pong\n",
      " 7.63: Pong returns Ping\n",
      " 7.77: Ping returns Pong\n",
      " 7.91: Pong returns Ping\n",
      " 8.08: Ping returns Pong\n",
      " 8.20: Pong returns Ping\n",
      " 8.34: Ping returns Pong\n",
      " 8.51: Pong returns Ping\n",
      " 8.73: Ping returns Pong\n",
      " 8.92: Pong returns Ping\n",
      " 9.01: Ping returns Pong\n",
      " 9.64: Pong returns Ping\n",
      " 9.79: Ping returns Pong\n",
      " 9.87: Pong returns Ping\n",
      "10.02: Ping returns Pong\n",
      "10.14: Pong returns Ping\n",
      "10.27: Ping returns Pong\n",
      "10.47: Pong returns Ping\n",
      "10.64: Ping returns and misses Pong\n",
      "13.84: Pong serves and misses Ping\n",
      "17.12: Ping serves Pong\n",
      "17.29: Pong returns and misses Ping\n",
      "20.59: Ping serves Pong\n",
      "20.73: Pong returns Ping\n",
      "20.85: Ping returns Pong\n",
      "20.97: Pong returns Ping\n",
      "21.25: Ping returns Pong\n",
      "21.40: Pong returns Ping\n",
      "21.55: Ping returns Pong\n",
      "21.65: Pong returns Ping\n",
      "21.65: Ping looses ball\n",
      "24.93: Ping serves Pong\n",
      "25.12: Pong returns Ping\n",
      "25.26: Ping returns Pong\n",
      "25.37: Pong returns Ping\n",
      "25.53: Ping returns Pong\n",
      "25.53: Pong looses ball\n",
      "28.89: Pong serves Ping\n",
      "29.00: Ping returns Pong\n",
      "29.23: Pong returns Ping\n",
      "29.45: Ping returns Pong\n",
      "29.45: Pong looses ball\n",
      "32.79: Pong serves Ping\n",
      "run! finished with 47 clock events, 0 sample steps, simulation time: 30.0\n",
      "Ping scored 5\n",
      "Pong scored 2\n"
     ]
    }
   ],
   "source": [
    "ping = Player(\"Ping\", 0.90, 0.90)\n",
    "pong = Player(\"Pong\", 0.90, 0.90)\n",
    "init!(ping, pong)\n",
    "init!(pong, ping)\n",
    "step!(ping, Start())\n",
    "\n",
    "Random.seed!(123)\n",
    "\n",
    "println(run!(𝐶, 30))\n",
    "println(\"Ping scored $(ping.score)\")\n",
    "println(\"Pong scored $(pong.score)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally we reset `𝐶` for further simulations."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "\"clock reset to t₀=0.0, sampling rate Δt=0.0.\""
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "reset!(𝐶)"
   ]
  }
 ],
 "metadata": {
  "@webio": {
   "lastCommId": null,
   "lastKernelId": null
  },
  "kernelspec": {
   "display_name": "Julia 1.2.0",
   "language": "julia",
   "name": "julia-1.2"
  },
  "language_info": {
   "file_extension": ".jl",
   "mimetype": "application/julia",
   "name": "julia",
   "version": "1.2.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
